
----------------------------------------------------------------
--- Copyright：  迷你玩-海马
--- Description：数据缓存服务器
----------跨服务器的mysql数据cache层----------------------------
----------将数据从逻辑服中分离出来，并提供共享内存加速----------
----------------------------------------------------------------

---接口
---read    读取数据 (默认加远程锁 可选不加)   key+hash  get value
---save    保存数据 (默认解远程锁 可选不解)   key+hash  set value
---del     删除数据 (别名hdel)

---mread   批量读取数据                    (key1+hash1 key2+hash2 key3+hash3)

---hlen           获得指定的key下hash的个数
---hlist          扫描时候使用，按key从小到大获得指定个数的key+hash      (保持和ssdb版一致)
---hlist_mysql    扫描时候使用，按id从小到大获得指定个数的key+hash+value (mysql-dc2落地模式专用,更加先进,会直接返回key+hash+value)
---hget_all       按key获取该key下所有hash和value数据

---clean_cache    直接清理cache


---lock_kh        主动加一个远程锁  (返回一个table or json)
---unlock_kh      主动解一个远程锁  (返回一个table or json)

---comm    通用复合接口
---comm2   测试接口

------------------------------------------------------------------
---  key hash 的数据结构：
---		key 123456
---			hash=1    value=vv1
---			hash=2    value=vv2
---			hash=3    value=vv3
-------------------------------------------------------------------


local assert     = assert
local require    = require
local bit        = bit
local coroutine  = coroutine
local debug      = debug
local io         = io
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local os         = os
local print      = print
local pcall      = pcall
local xpcall     = xpcall
local rawget     = rawget
local rawset     = rawset
local select     = select
local string     = string
local table      = table
local tonumber   = tonumber
local tostring	 = tostring
local error      = error
local type       = type
local unpack     = unpack
local setmetatable = setmetatable
local getmetatable = getmetatable
local ngx = ngx

local string_char  = string.char
local string_sub   = string.sub
local string_byte  = string.byte
local string_gsub  = string.gsub
local string_find  = string.find
local string_upper = string.upper

local math_fmod   = math.fmod
local math_floor  = math.floor
local math_pow    = math.pow
local math_abs    = math.abs

local table_loadstring = table.loadstring

local uu           = require( "modules.commhm.utils" )
local log          = require( "modules.commhm.log" )
local ns_time      = require( "modules.commhm.time" )
local ns_network   = require( "modules.commhm.network" )
local ns_http      = require( "modules.commhm.resty.http" )

local ns_const_vars  = require("modules.commhm.const_vars")

local ns_config       = require( "modules.data_cache.data_cache_config" )
local gg              = require( "modules.data_cache.data_cache_mysql_gg" )
local ns_comm_gg      = require( "modules.data_cache.comm_gg" )
local ns_auth_checker = require( "modules.data_cache.auth_checker" )

local ns_share_dict_checker = require("modules.commhm.framework.share_dict_checker");


local  __HASH_NOT_NUMBER__ = 9991    --hash值非数字时的替代，用来兼容旧的mission map的非数值hash


local FOO = {
	_VERSION = '0.12.1',
	cmds = {},

	worker_id = ngx.worker.id(),
};



FOO.enter = function()
	ngx.ctx.m_params = ngx.req.get_uri_args();
	local args_ = ngx.ctx.m_params

	if  ns_comm_gg.check_ip_allow_by_name() > 0 then
		return
	end

	ngx.header["Carry"] = args_.carry   --防止网络请求串流

	--log.debug( "headers1= " .. table.tostring(ngx.req.get_headers()) );

	local cmd_ = args_.cmd
	if  cmd_  and FOO.cmds[cmd_]  then
		if  ns_auth_checker.check_data_cache_auth() then
			gg.init()
			FOO.cmds[cmd_]( args_ )
			gg.final()
		else
			log.error("auth error:" .. (cmd_ or 'nil') )
		end
	else
		log.error("action error:" .. (cmd_ or 'nil') )
	end

	--log.debug( "headers2= " .. table.tostring(ngx.resp.get_headers()) );

end



-----------------------------通用接口----------------
---POST
FOO.cmds.comm = function( args_ )
	log.debug( "call dc - comm" )

	--参数在post里
	ngx.req.read_body();
	local post_args_ = ngx.req.get_post_args();

	--合并参数( 优先post覆盖get )
	for k, v in pairs( post_args_ ) do
		args_[k] = v
	end

	args_.cmd = args_.func   --顶掉comm
	uu.var_dump( args_ )

	if  FOO.cmds[ args_.cmd ] then
		log.debug( "call dc - " .. args_.cmd )
		FOO.cmds[ args_.cmd ]( args_ )
	else
		log.error("comm action error:" .. ( args_.cmd or 'nil') )
	end

end


--GET 用来测试的接口
FOO.cmds.comm2 = function( args_ )
	log.debug( "call dc - comm2" )
	--参数在get里
	args_.cmd = args_.func   --顶掉comm

	uu.var_dump( args_ )

	if  FOO.cmds[ args_.cmd ] then
		log.debug( "call dc - " .. args_.cmd )
		FOO.cmds[ args_.cmd ]( args_ )
	end
end



-----------------------------------------------------------------
--存储数据
--_M.save = function( self, select_, key, hash, value, err_call_back, mysql_delay_ )
FOO.cmds.save = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local hash_   = tonumber(args_.hash)   --mysql short int4
	local select_ = FOO.private_get_select( args_ )
	local ver_    = args_.ver

	if  not hash_ then
		key_, hash_ = FOO.private_reset_empty_hash( args_.key, args_.hash )
	end

	local len_
	local value_
	if  args_.value then
		value_ = args_.value   --一定是string
	elseif args_.post_value then
		--post里的所有内容都是value, post_value是长度
		len_ = tonumber(args_.post_value) or 0
		ngx.req.read_body();
		value_ = ngx.req.get_body_data()
	else
		--post里，按标准的submit格式: value=xxxxx&len=5 获得
		ngx.req.read_body();
		local post_args_ = ngx.req.get_post_args();
		uu.var_dump( post_args_ )
		value_= post_args_["value"]
		if  post_args_["len"] then
			len_ = tonumber(post_args_["len"]) or 0
		end
	end

	if  len_ and (len_ <= 0) then
		uu.return_string( "NO_V_LEN0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end


	---if need decode base64
	if  args_.isbase64 == '1' then
		local value_64_ = ngx.decode_base64(value_)
		if  value_64_ then
			log.day_list_simple( "dc_mysql", "base64_decode_ok", #value_, #value_64_ )
			value_ = value_64_
		else
			log.day_list_simple( "dc_mysql", "error_base64", key_, hash_, select_, value_ )
			uu.return_string( "ERROR_BASE64", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
	end


	---check vtype
	if  args_.vtype then
		-- 格式能校验通过，就不用校验len
		local pass_ = false
		if     args_.vtype == 'json' then
			if  uu.stringIsJson( value_ ) then
				pass_ = true
			end
		elseif args_.vtype == 'lua' then
			if  uu.stringIsLuaTable( value_ ) then
				pass_ = true
			end
		end
		if  not pass_ then
			log.day_list( "dc_mysql", "error_post_value_len", #value_, args_.vtype, '\n', value_ )
			uu.return_string( "POST_VALUE_LEN_ERROR", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
	else
		-- 需要校验len
		if  len_ and (not (#value_ == len_)) then
			log.day_list( "dc_mysql", "dc_client", "post_value_len_error", len_, #value_ )
		end
	end


	-- 校验必要参数
	if  not( value_ and #value_ > 0 ) then
		log.day_list_simple( "dc_mysql", "error_value_len0", key_, hash_, select_, uu.to_str(value_) )
		uu.return_string( "PARAM_VALUE_LEN0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end


	-- 写入数据
	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_save( select_, key_, hash_, value_, ver_, FOO.error_exit,
				args_.mysql_delay, args_.read_only )
		if  ret_ == "OK" then
			uu.return_string( "OK" )
		else
			uu.return_string( ret_ or "", ns_const_vars.ERROR_CODE.PARAM )
		end
		ns_comm_gg.add_byte_tj( name_..'_dc2', "save_" .. hash_, 0, select_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf name not exit=" .. name_ )
	end

end


-----------------------------------------------------------------
--获取数据
--_M.read = function( self, select_, key, hash, err_call_back )
FOO.cmds.read = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local hash_   = tonumber(args_.hash)   --mysql short int4
	local select_ = FOO.private_get_select( args_ )

	if  not hash_ then
		key_, hash_ = FOO.private_reset_empty_hash( args_.key, args_.hash )
	end

	if  args_.no_cache == 'y' then
		--log.day_list_short( "dc_mysql", "read_no_shared_cache", name_, select_, key_, hash_ );
		ngx.ctx.no_shared_cache = 'y'   --不存shared缓存
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_, data_ = mysql_mgr_:API_mysql_read( select_, key_, hash_, FOO.error_exit,
				args_.read_only, args_.lock_timeout )
		if  ret_ == 0 then
			if  ngx.ctx.__lock_id__ then
				--log.day_list_simple( "dc_mysql", "set_lock_id", select_, key_, hash_, ngx.ctx.__lock_id__ )
				ngx.header["LockId"] = ngx.ctx.__lock_id__   ---返回锁id
			end
			uu.return_string( data_ )
			ns_comm_gg.add_byte_tj( name_..'_dc2', "read_" .. hash_, #data_, select_ )
			if  #data_ <= 0 then
				log.day_list_simple( "dc_mysql", "error_read_len0", select_, key_, hash_, data_ )
			end
		else
			uu.return_string( data_, (ret_ or ns_const_vars.ERROR_CODE.PARAM) )
		end
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



-- 加锁后获得数据 (返回值是一个大json结构体) (目前账号服解耦专用)
-- _M.mread = function( self, select_ )
FOO.cmds.lock_read = function( args_ )
	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local select_ = FOO.private_get_select( args_ )
	local hash_   = tonumber(args_.hash)   --mysql short int4

	if  not (name_ and select_ and key_ and hash_) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local fret_ = {}

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		--- 先逐个拿到锁
		local kh_ = args_.key .. '_h' .. hash_     --key .. "_h" .. hash
		local ret_, err_ = mysql_mgr_:API_mysql_try_get_resty_lock( select_, kh_, args_.lock_timeout )
		if  err_ then
			--- 锁获取失败
			uu.return_table( { ret=ret_, hash=hash_, err='lock_err', msg=err_ } )
			return
		end
		fret_.lock_kh_id = ret_

		--- 拿到锁后，开始获得数据
		local ret_, data_ = mysql_mgr_:API_mysql_read( select_, key_, hash_,
				FOO.error_exit, '1', args_.lock_timeout )
		if  ret_ == 0 then
			--uu.return_string( data_ )
			fret_.data = data_
			fret_.ret  = 0
			ns_comm_gg.add_byte_tj( name_..'_dc2', "lock_read_" .. hash_, #data_, select_ )
			if  #data_ <= 0 then
				log.day_list_simple( "dc_mysql", "error_lock_read_len0", select_, key_, hash_, data_ )
			end
		else
			--uu.return_string( data_, (ret_ or ns_const_vars.ERROR_CODE.PARAM) )
			fret_.data = data_
			fret_.ret  = (ret_ or ns_const_vars.ERROR_CODE.PARAM)
		end

		uu.return_table( fret_ )
	end
end




-- 批量加锁，并获得数据 (返回值是一个大json结构体)(目前账号服解耦专用)
-- _M.mread = function( self, select_ )
FOO.cmds.lock_mread = function( args_ )
	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local select_ = FOO.private_get_select( args_ )

	local hash_list_ = uu.split( args_.hash_list, ',' )

	if  not (name_ and select_ and key_ and type(hash_list_) == 'table' and #hash_list_>0) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local fret_ = {}

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		--- 先逐个拿到锁
		for i=1, #hash_list_ do
			local hash_ = hash_list_[i]
			local kh_ = args_.key .. '_h' .. hash_     --key .. "_h" .. hash
			local ret_, err_ = mysql_mgr_:API_mysql_try_get_resty_lock( select_, kh_, args_.lock_timeout )
			if  err_ then
				--- 某一个锁获取失败
				uu.return_table( { ret=ret_, hash=hash_, err='lock_err', msg=err_ } )
				return
			end
			fret_[ hash_ ] = { lock_kh_id=ret_ }
		end

		--- 全部拿到锁后，开始获得数据
		for i=1, #hash_list_ do
			local hash_ = hash_list_[i]
			local ret_, data_ = mysql_mgr_:API_mysql_read( select_, key_, hash_,
					FOO.error_exit, '1', args_.lock_timeout )
			if  ret_ == 0 then
				--uu.return_string( data_ )
				fret_[ hash_ ].data = data_
				fret_[ hash_ ].ret  = 0
				ns_comm_gg.add_byte_tj( name_..'_dc2', "lock_mread_" .. hash_, #data_, select_ )
				if  #data_ <= 0 then
					log.day_list_simple( "dc_mysql", "error_lock_mread_len0", select_, key_, hash_, data_ )
				end
			else
				--uu.return_string( data_, (ret_ or ns_const_vars.ERROR_CODE.PARAM) )
				fret_[ hash_ ].data = data_
				fret_[ hash_ ].ret  = (ret_ or ns_const_vars.ERROR_CODE.PARAM)
			end
		end

		fret_.ret = 0
		uu.return_table( fret_ )
	end

end




--批量获得数据
-- _M.mread = function( self, select_, all_kv_list, err_call_back )
FOO.cmds.mread = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	if  not name_ then
		uu.return_string( "PARAM_ERROR1", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	--local key_hash_ = all_kv_list[i].key .. "_h" .. all_kv_list[i].hash
	local all_kv_list
	if  args_.kv_list then
		local t_ = uu.split( args_.kv_list, "-" )
		if  #t_ > 0 then
			all_kv_list = {}
			for i=1, #t_, 2 do
				all_kv_list[ #all_kv_list + 1 ] = { key=t_[i],  hash=t_[i+1] }
			end
		end
		uu.var_dump( all_kv_list )
	else
		--read post
		ngx.req.read_body();
		local post_args_ = ngx.req.get_post_args();
		uu.var_dump( post_args_ )
		if  post_args_['kv_list'] then
			all_kv_list = uu.safe_loadstring ( post_args_['kv_list'] )
		end
	end

	if (not all_kv_list) or #all_kv_list <= 0 then
		uu.return_string( "PARAM_ERROR_LIST0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	--计算落地的select
	local select_ = FOO.private_get_select_mread( args_, all_kv_list )
	if  not select_ then
		uu.return_string( "PARAM_ERROR2", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_mread( select_, all_kv_list,  FOO.error_exit )
		uu.var_dump( ret_ )
		local len_ = uu.return_table( ret_ )
		ns_comm_gg.add_byte_tj( name_..'_dc2', "mread", len_, select_, #all_kv_list )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



--批量保存同一个key下的多个hash-value
FOO.cmds.msave_key = function( args_ )
	local name_   = args_.name      --mysql name: ma map posting
	if  not name_ then
		uu.return_string( "PARAM_ERROR1", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local key_    = args_.key
	local select_ = FOO.private_get_select( args_ )


	local kv_list_data_str_, kv_list_data_
	-- 获得post请求里的内容
	if args_.post_value then
		--post里的所有内容都是value, post_value是长度
		local len_ = tonumber(args_.post_value) or 0
		ngx.req.read_body()
		kv_list_data_str_ = ngx.req.get_body_data()
		if  kv_list_data_str_ and len_ == #kv_list_data_str_ then
			log.debug( 'get_body_data len check ok' )
		else
			log.error( 'get_body_data len check fail' )
		end
	else
		ngx.req.read_body()
		local post_args_ = ngx.req.get_post_args()
		if  post_args_ and  post_args_['kv_list'] then
			kv_list_data_str_ = post_args_['kv_list']
		end
	end

	-- 转换格式
	if  args_.vtype == 'json' then
		kv_list_data_ = uu.json2table( kv_list_data_ or '' )
	else
		kv_list_data_ = table_loadstring( kv_list_data_ or '' )
	end

	--[[
	if  ns_config.debug == 1 then
		log.day_list_short( 'dc_mysql', "msave_key", uu.to_str(args_),  uu.to_str(kv_list_data_) )

		--{{{ 测试数据
		if  not kv_list_data_ then
			log.debug( "decode kv_list_data error" )
			if  ngx.ctx.m_params.test_data then
				if  ngx.ctx.m_params.test_data == '0' then
					kv_list_data_ = {
						{ hash=101, value='xxx00', ver=0, len=5, lock_kh_id=10001 },
						{ hash=102, value='yyy00', ver=0, len=5, lock_kh_id=10002 },
						{ hash=103, value='zzz00', ver=0, len=5, lock_kh_id=10003 },
					}
				elseif  ngx.ctx.m_params.test_data == '1' then
					kv_list_data_ = {
						{ hash=101, value='xxx11', ver=1, len=5, lock_kh_id=10001 },
						{ hash=102, value='yyy11', ver=1, len=5, lock_kh_id=10002 },
						{ hash=103, value='zzz11', ver=1, len=5, lock_kh_id=10003 },
					}
				elseif  ngx.ctx.m_params.test_data == '2' then
					kv_list_data_ = {
						{ hash=101, value='xxx22', ver=2, len=5, lock_kh_id=10001 },
						{ hash=102, value='yyy22', ver=2, len=5, lock_kh_id=10002 },
						{ hash=103, value='zzz22', ver=2, len=5, lock_kh_id=10003 },
					}
				end
				log.debug( "use test_data, set kv_list_data=" .. uu.to_str( kv_list_data_ ) )
			end
		end
		--}}}
	end
	--]]


	if  not ( type(kv_list_data_)=='table' and #kv_list_data_ > 0 ) then
		log.day_list_short( 'dc_mysql', "msave_key_kv_list_decode_fail", uu.to_str(args_), '\n' .. kv_list_data_str_ )
		uu.return_string( "PARAM_ERROR2_" .. (args_.vtype or 'lua') , ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	for i=1, #kv_list_data_ do
		--逐个检查len
		if  not ( kv_list_data_[i].hash and kv_list_data_[i].value ) then
			uu.return_string( "PARAM_ERROR3", ns_const_vars.ERROR_CODE.PARAM )
			return
		end

		--检查长度
		if  kv_list_data_[i].len then
			local len_   = kv_list_data_[i].len
			local value_ = kv_list_data_[i].value

			if  args_.vtype then
				--检查格式
				local pass_ = false
				if     args_.vtype == 'json' then
					if  uu.stringIsJson( value_ ) then
						--log.day_list( "dc_mysql", "msave_warning_json_value_len", #value_, args_.vtype )
						pass_ = true
					end
				elseif args_.vtype == 'lua' then
					if  uu.stringIsLuaTable( value_ ) then
						--log.day_list( "dc_mysql", "msave_warning_lua_value_len", #value_, args_.vtype )
						pass_ = true
					end
				end

				if  not pass_ then
					log.day_list( "dc_mysql", "error_post_value_len", len_, #value_, args_.vtype, '\n', value_ )
					uu.return_string( "MSAVE_POST_VALUE_LEN_ERROR", ns_const_vars.ERROR_CODE.PARAM )
					return
				end
			else
				-- 没有vtype，需要校验len
				if  not (len_ == #value_) then
					log.day_list( "dc_mysql", "msave_warning_value_len", len_, #value_ )
				end
			end
		end
	end


	---检查完毕后，逐个写入
	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  not mysql_mgr_ then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
		return
	end


	for i=1, #kv_list_data_ do
		local value_ = kv_list_data_[i].value
		local hash_   = tonumber(kv_list_data_[i].hash)   --mysql short int4

		if  not hash_ then
			local new_key_, new_hash_ = FOO.private_reset_empty_hash( key_, kv_list_data_[i].hash )
			hash_ = new_hash_
		end

		local ver_   = kv_list_data_[i].ver

		if  kv_list_data_[i].lock_kh_id then
			ngx.ctx.m_params.lock_kh_id = kv_list_data_[i].lock_kh_id    -- 解锁时候会用到
		else
			ngx.ctx.m_params.lock_kh_id = nil
		end

		local ret_ = mysql_mgr_:API_mysql_save( select_, key_, hash_, value_, ver_, FOO.error_exit,
				args_.mysql_delay, args_.read_only )

		if  ns_config.debug == 1 then
			log.day_list_short( 'dc_mysql', "msave_key_ret", select_, key_, hash_, ver_, ret_, '\n' .. value_ )
		end

		if  not (ret_ == "OK") then
			uu.return_string( ret_ or "", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
		ns_comm_gg.add_byte_tj( name_..'_dc2', "msave_" .. hash_, 0, select_ )
	end

	uu.return_string( "OK" )

end





-- 删除一个key下的一个hash
--_M.del = function( self, select_, key, hash, err_call_back )
FOO.cmds.del = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local hash_   = tonumber(args_.hash)   --mysql short int4
	local select_ = FOO.private_get_select( args_ )

	if  not hash_ then
		key_, hash_ = FOO.private_reset_empty_hash( args_.key, args_.hash )
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_del( select_, key_, hash_, FOO.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end

FOO.cmds.hdel = FOO.cmds.del ---别名


-- 一个key下有多少个hash值
--_M.hlen = function( self, select_, key, err_call_back)
FOO.cmds.hlen = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local select_ = FOO.private_get_select( args_ )

	if  not ( name_ and select_ and key_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_hlen( select_, key_, FOO.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end




-- 从start开始遍历所有hash key
--_M.hlist = function( self, select_, start_, err_call_back)
FOO.cmds.hlist = function( args_ )

	local name_   = args_.name    --mysql name: ma map posting
	local start_  = args_.start
	local size_   = tonumber(args_.size) or 10
	local select_ = FOO.private_get_select( args_ )

	if  not ( name_ and select_ and start_ and size_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_hlist( select_, start_, size_, FOO.error_exit )
		--uu.return_string( table.tostring(ret_) )
		uu.return_table( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


-- 从start开始遍历所有hash key  (跟ssdb - hlist 扫描模式不同)
-- 扫描时候使用，按id从小到大获得指定个数的key+hash+value (mysql-dc2落地模式专用,更加先进,会直接返回key+hash+value)
FOO.cmds.hlist_mysql = function( args_ )

	local name_   = args_.name    --mysql name: ma map posting
	local start_  = tonumber(args_.start) or 0
	local size_   = tonumber(args_.size) or 10
	local select_ = FOO.private_get_select( args_ )

	if  not ( name_ and select_ and start_ and size_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_hlist_id( select_, start_, size_, FOO.error_exit )
		uu.return_table( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end




--获得一个key下的所有值
--_M.hget_all = function( self, select_, key, err_call_back )
FOO.cmds.hget_all = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local select_ = FOO.private_get_select( args_ )

	if  not ( name_ and select_ and key_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_ = mysql_mgr_:API_mysql_hget_all( select_, key_, FOO.error_exit )
		--uu.return_string( table.tostring(ret_) )
		uu.return_table( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


--清理缓存，清理后，读请求会重新拉取mysql
FOO.cmds.clean_cache = function( args_ )

	local name_   = args_.name      --mysql name: ma map posting
	local key_    = args_.key
	local hash_   = tonumber(args_.hash)   --mysql short int4
	local select_ = FOO.private_get_select( args_ )

	if  not hash_ then
		key_, hash_ = FOO.private_reset_empty_hash( args_.key, args_.hash )
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		mysql_mgr_:API_mysql_clean_shared_cache( select_, key_, hash_ )
		uu.return_string( "OK" )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end
end



--加锁 一般read的时候会加锁，特殊情况下可以使用lock_kh来主动获得一个锁
FOO.cmds.lock_kh = function( args_ )
	local name_   = args_.name                         --mysql name: ma map posting

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	local select_ = FOO.private_get_select( args_ )
	if  not ( name_ and select_ and kh_ ) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_, err_ = mysql_mgr_:API_mysql_try_get_resty_lock( select_, kh_, args_.lock_timeout )
		if  err_ then
			uu.return_table( { ret=ret_, msg=err_ } )
		else
			uu.return_table( { ret=0,    lock_kh_id=ret_ } )
		end
	else
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
	end
end



--解锁
FOO.cmds.unlock_kh = function( args_ )
	local name_   = args_.name      --mysql name: ma map posting

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	local select_ = FOO.private_get_select( args_ )
	if  not ( name_ and select_ and kh_ ) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  mysql_mgr_ then
		local ret_, err_ = mysql_mgr_:API_mysql_unlock_resty_lock( select_, kh_ )
		uu.return_table( { ret=ret_, msg=err_ } )
	else
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
	end

end



--批量解锁
FOO.cmds.unlock_khs = function( args_ )
	local name_   = args_.name      --mysql name: ma map posting

	--校验 hash_list
	if  not args_.hash_list then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR_hash_list" } )
		return
	end

	local  hash_list_ = uu.split( args_.hash_list, ',' )
	if  not (type(hash_list_) == 'table' and #hash_list_ > 0) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR_hash_list_size" } )
		return
	end

	local lock_kh_id_list_
	if  args_.lock_kh_id_list then
		lock_kh_id_list_ = uu.split( args_.lock_kh_id_list, ',' )
		if  not ( type(lock_kh_id_list_) == 'table' and #lock_kh_id_list_ == #hash_list_ ) then
			uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR_lock_kh_id_list_size" } )
			return
		end
	end


	local select_ = FOO.private_get_select( args_ )
	if  not ( name_ and select_ ) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  not mysql_mgr_ then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
		return
	end


	--逐个解锁
	local err_list_ = {}  ---失败列表
	for i=1, #hash_list_ do
		local kh_ = args_.key .. '_h' .. hash_list_[i]
		if  lock_kh_id_list_ then
			ngx.ctx.m_params.lock_kh_id = lock_kh_id_list_[i]    -- 解锁时候会用到
		end
		local ret_, err_ = mysql_mgr_:API_mysql_unlock_resty_lock( select_, kh_ )
		if  ret_ ~= 0 then
			log.error( "unlock_resty_lock_error_" .. ret_ .. err_ )
			err_list_[ #err_list_ + 1 ] = { hash=hash_list_[i], msg=err_ }
		end
	end

	local fret_ = {ret=0, msg="OK" }
	if  #err_list_ > 0 then
		fret_.error_list = err_list_
	end
	uu.return_table( fret_ )

end




-- 设置一个mysql的绑定关系
FOO.cmds.set_bind = function( args_ )
	local name_ = args_.name      --mysql name: acc_name_uin
	local str_  = args_.str
	local uin_  = tonumber(args_.uin) or 0
	if  not (name_ and str_ and uin_ and #str_>0 and uin_>=1000) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  not mysql_mgr_ then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
		return
	end

	local ret_, err_ = mysql_mgr_:API_mysql_set_bind( 0, str_, uin_, FOO.error_exit )
	uu.return_table( { ret=ret_, msg=err_ } )
end



-- 获得mysql的绑定关系
FOO.cmds.get_bind = function( args_ )
	local name_ = args_.name      --mysql name: acc_name_uin
	local str_  = args_.str
	local uin_  = args_.uin

	if  not (name_ and (str_ or uin_)) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local mysql_mgr_ = gg.mysqlMgrDcList[ name_ ]
	if  not mysql_mgr_ then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
		return
	end

	local ret_, msg_ = mysql_mgr_:API_mysql_get_bind( 0, str_, uin_, FOO.error_exit )
	if  ret_ == 0 then
		if  type( msg_ ) == 'table' and type(msg_[1]) == 'table' and msg_[1].uin and msg_[1].str then
			uu.return_table( { ret=ret_, msg='ok', uin=msg_[1].uin,  str=msg_[1].str } )
		else
			uu.return_table( { ret=1, msg='not_found' } )
		end
	else
		uu.return_table( { ret=ret_, msg=msg_ } )
	end

end


------------------------------------------------------
-- 异常退出
FOO.error_exit = function( err, trace_info_ )
	ngx.status = 500   --内部服务器（HTTP-Internal Server Error）
	ngx.print( err )
	ngx.eof()

	-- 析构
	log.error(err, trace_info_);
	gg.final()

	ns_time.print_cost_all();
	ngx.exit(0)    --完全退出
end


-------------------------------------------private
FOO.private_get_select = function( args_ )
	--- mysql 版本的 select 一定是一个数字
	local select_
	if  args_.select then
		select_ = tonumber(args_.select)
	elseif args_.key then
		--select_ = tonumber(args_.key)
		select_ = ns_comm_gg.get_select_by_string( args_.key )
	elseif args_.kh then
		select_ = ns_comm_gg.get_select_by_string( args_.kh )
	else
		--error
	end
	return select_
end


---mread模式
FOO.private_get_select_mread = function( args_, kv_list_ )
	-- uu.var_dump( kv_list_ )
	-- mysql 版本的 select 一定是一个数字
	local select_
	if  args_.select then
		select_ = tonumber(args_.select)
	elseif kv_list_ and kv_list_[1] then
		--select_ = tonumber( kv_list_[1].key )
		select_ = ns_comm_gg.get_select_by_string( kv_list_[1].key )
	end
	return select_
end



---尝试修复hash非数字的情况
FOO.private_reset_empty_hash = function( key_, hash_ )
	local key_new_
	-- 兼容旧的hash非数字，比如  mission: mis1 mis2 mis3 mis4   map: 99_comm_global_data
	if  hash_ and #hash_>0 then
		key_new_  = key_ .. '_' .. hash_
		log.day_list_simple( "reset_hash", "ok", key_, hash_, key_new_, __HASH_NOT_NUMBER__ )
		return key_new_, __HASH_NOT_NUMBER__
	else
		log.day_list_simple( "reset_hash", "fail", key_, hash_ )
		return
	end
end


---------定时任务 统计 30s
FOO.time_every_tj = function()
	--1
	ns_comm_gg.print_double_check_tj( 'mysql' )

	--2
	ns_comm_gg.print_byte_tj( 'mysql', 'dc2' )
end


------------初始化
FOO.init = function()

	ns_comm_gg.timer_every( 30, FOO.time_every_tj )

	---定时检查share_dict剩余空间
	local shared_dict_name_list_ = { "miniw", "ssdb", "map" }
	for i=0, 9 do
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "ssdb"  .. i
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "miniw" .. i
	end

	if  ns_config.mysql then
		for k, v in pairs( ns_config.mysql ) do
			for i=0, 9 do
				if  type(k) == 'string' then
					shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = k .. i
				end
			end
		end
	end
	ns_share_dict_checker.start_check_by_timer( shared_dict_name_list_, 602, "dc2" )

end


FOO.init()
return FOO;
